1 using System;
2 using
System.Collections.Generic;
3 using
UnityEngine;
4 using
UnityEngine.Assertions;
5 using
Object = UnityEngine.Object;
6 using
Random = UnityEngine.Random;
7
8 namespace
ProceduralToolkit.Examples
9 {
10     
public enum BrickSize
11     {
12         Narrow,
13         Wide,
14     }

15
16     ///
<summary>
17     ///
Breakout clone with procedurally generated levels
18     ///
</summary>
19     
public class Breakout
20     {
21         
[Serializable]
22         
public class Config
23         {
24             
public int wallWidth = 9;
25             
public int wallHeight = 7;
26             
public int wallHeightOffset = 5;
27             
public float paddleWidth = 1;
28             
public float ballSize = 0.5f;
29             
public float ballVelocityMagnitude = 5;
30             
public Gradient gradient;
31         }
32
33         
private const float brickColorMinValue = 0.6f;
34         
private const float brickColorMaxValue = 0.8f;
35
36         
private const float brickHeight = 0.5f;
37         
private const float paddleHeight = 0.5f;
38         
private const float paddleSpeed = 25f;
39         
private const float ballRadius = 0.5f;
40         
private const float ballForce = 200;
41
42         
private Config config;
43
44         
private Transform bricksContainer;
45         
private Sprite whiteSprite;
46         
private PhysicsMaterial2D bouncyMaterial;
47         
private GameObject borders;
48         
private GameObject paddle;
49         
private Transform paddleTransform;
50         
private GameObject ball;
51         
private Transform ballTransform;
52         
private Rigidbody2D ballRigidbody;
53         
private List<GameObject> bricks = new List<GameObject>();
54
55         
private Dictionary<BrickSize, float> sizeValues = new Dictionary<BrickSize, float>
56         {
57             {BrickSize.Narrow,
0.5f},
58             {BrickSize.Wide,
1f},
59         };
60
61         
public Breakout()
62         {
63             bricksContainer =
new GameObject("Bricks").transform;
64
65             
// Generate texture and sprite for bricks, paddle and ball
66             
var texture = Texture2D.whiteTexture;
67             whiteSprite = Sprite.Create(texture,
68                 rect:
new Rect(0, 0, texture.width, texture.width),
69                 pivot:
new Vector2(0.5f, 0.5f),
70                 pixelsPerUnit: texture.width);
71
72             
// Bouncy material for walls, paddle and everything else
73             bouncyMaterial =
new PhysicsMaterial2D {name = "Bouncy", bounciness = 1, friction = 0};
74         }
75
76         
public void Generate(Config config)
77         {
78             Assert.IsTrue(config.wallWidth >
0);
79             Assert.IsTrue(config.wallHeight >
0);
80             Assert.IsTrue(config.paddleWidth >
0);
81             Assert.IsTrue(config.ballSize >
0);
82
83             
this.config = config;
84             ResetLevel();
85         }
86
87         
public void Update()
88         {
89             
float delta = Input.GetAxis("Horizontal")*Time.deltaTime*paddleSpeed;
90             paddleTransform.position +=
new Vector3(delta, 0);
91
92             
// Prevent paddle from penetrating walls
93             
float halfWall = (config.wallWidth - 1)/2f;
94             
if (paddleTransform.position.x > halfWall)
95             {
96                 paddleTransform.position =
new Vector3(halfWall, 0);
97             }
98             
if (paddleTransform.position.x < -halfWall)
99             {
100                 paddleTransform.position =
new Vector3(-halfWall, 0);
101             }
102
103             
// Ball should move with constant velocity
104             ballRigidbody.velocity = ballRigidbody.velocity.normalized*config.ballVelocityMagnitude;
105
106             
if (ballTransform.position.y < -0.1f)
107             {
108                 ResetLevel();
109             }
110
111             
float angle = Vector2.Angle(ballRigidbody.velocity, Vector2.right);
112             
if (angle < 30 || angle > 150)
113             {
114                 
// Prevent ball from bouncing between walls
115                 KickBallInRandomDirection();
116             }
117         }
118
119         
private void ResetLevel()
120         {
121             GenerateBorders();
122             GenerateLevel();
123             GeneratePaddle();
124             GenerateBall();
125         }
126
127         
private void GenerateBorders()
128         {
129             
if (borders != null)
130             {
131                 Object.Destroy(borders);
132             }
133             borders =
new GameObject("Border");
134             
float bordersHeight = config.wallHeightOffset + config.wallHeight/2 + 1 + config.ballSize;
135             
float bordersWidth = config.wallWidth + 1;
136
137             
// Bottom
138             CreateBoxCollider(offset:
new Vector2(0, -1 - config.ballSize/2),
139                 size:
new Vector2(bordersWidth, 1));
140             
// Left
141             CreateBoxCollider(offset:
new Vector2(-bordersWidth/2f, bordersHeight/2f - 0.5f - config.ballSize/2),
142                 size:
new Vector2(1, bordersHeight + 1));
143             
// Right
144             CreateBoxCollider(offset:
new Vector2(bordersWidth/2f, bordersHeight/2f - 0.5f - config.ballSize/2),
145                 size:
new Vector2(1, bordersHeight + 1));
146             
// Top
147             CreateBoxCollider(offset:
new Vector2(0, bordersHeight - config.ballSize/2),
148                 size:
new Vector2(bordersWidth, 1));
149         }
150
151         
private void CreateBoxCollider(Vector2 offset, Vector2 size)
152         {
153             
var collider = borders.AddComponent<BoxCollider2D>();
154             collider.sharedMaterial = bouncyMaterial;
155             collider.offset = offset;
156             collider.size = size;
157         }
158
159         
private void GenerateLevel()
160         {
161             
// Destroy existing bricks
162             
foreach (var brick in bricks)
163             {
164                 Object.Destroy(brick);
165             }
166             bricks.Clear();
167
168             
for (int y = 0; y < config.wallHeight; y++)
169             {
170                 
// Select color for current line
171                 
var currentColor = new ColorHSV(config.gradient.Evaluate(y/(config.wallHeight - 1f)));
172
173                 
// Generate brick sizes for current line
174                 List<BrickSize> brickSizes = FillWallWithBricks(config.wallWidth);
175
176                 Vector3 leftEdge = Vector3.left*config.wallWidth/
2 +
177                                    Vector3.up*(config.wallHeightOffset + y*brickHeight);
178                 
for (int i = 0; i < brickSizes.Count; i++)
179                 {
180                     
var brickSize = brickSizes[i];
181                     
var position = leftEdge + Vector3.right*sizeValues[brickSize]/2;
182
183                     
// Randomize tint of current brick
184                     
float colorValue = Random.Range(brickColorMinValue, brickColorMaxValue);
185                     Color color = currentColor.WithV(colorValue).ToColor();
186
187                     bricks.Add(GenerateBrick(position, color, brickSize));
188                     leftEdge.x += sizeValues[brickSize];
189                 }
190             }
191         }
192
193         
private List<BrickSize> FillWallWithBricks(float width)
194         {
195             
// https://en.wikipedia.org/wiki/Knapsack_problem
196             
// We are using knapsack problem solver to fill fixed width with bricks of random width
197             Dictionary<BrickSize,
int> knapsack;
198             
float knapsackWidth;
199             
do
200             {
201                 
// Prefill knapsack to get nicer distribution of widths
202                 knapsack = GetRandomKnapsack(width);
203                 
// Calculate sum of brick widths in knapsack
204                 knapsackWidth = KnapsackWidth(knapsack);
205             }
while (knapsackWidth > width);
206
207             width -= knapsackWidth;
208             knapsack = PTUtils.Knapsack(sizeValues, width, knapsack);
209             
var brickSizes = new List<BrickSize>();
210             
foreach (var pair in knapsack)
211             {
212                 
for (var i = 0; i < pair.Value; i++)
213                 {
214                     brickSizes.Add(pair.Key);
215                 }
216             }
217             brickSizes.Shuffle();
218             
return brickSizes;
219         }
220
221         
private Dictionary<BrickSize, int> GetRandomKnapsack(float width)
222         {
223             
var knapsack = new Dictionary<BrickSize, int>();
224             
foreach (var key in sizeValues.Keys)
225             {
226                 knapsack[key] = (
int) Random.Range(0, width/3);
227             }
228             
return knapsack;
229         }
230
231         
private float KnapsackWidth(Dictionary<BrickSize, int> knapsack)
232         {
233             
float knapsackWidth = 0f;
234             
foreach (var key in knapsack.Keys)
235             {
236                 knapsackWidth += knapsack[key]*sizeValues[key];
237             }
238             
return knapsackWidth;
239         }
240
241         
private GameObject GenerateBrick(Vector3 position, Color color, BrickSize size)
242         {
243             
var brick = new GameObject("Brick");
244             brick.transform.position = position;
245             brick.transform.parent = bricksContainer;
246             brick.transform.localScale =
new Vector3(sizeValues[size], brickHeight);
247
248             
var brickRenderer = brick.AddComponent<SpriteRenderer>();
249             brickRenderer.sprite = whiteSprite;
250             brickRenderer.color = color;
251
252             
var brickCollider = brick.AddComponent<BoxCollider2D>();
253             brickCollider.sharedMaterial = bouncyMaterial;
254             brick.AddComponent<Brick>();
255             
return brick;
256         }
257
258         
private void GeneratePaddle()
259         {
260             
if (paddle == null)
261             {
262                 paddle =
new GameObject("Paddle");
263                 paddleTransform = paddle.transform;
264
265                 
var paddleRenderer = paddle.AddComponent<SpriteRenderer>();
266                 paddleRenderer.sprite = whiteSprite;
267                 paddleRenderer.color = Color.black;
268
269                 
var paddleCollider = paddle.AddComponent<BoxCollider2D>();
270                 paddleCollider.sharedMaterial = bouncyMaterial;
271             }
272
273             paddleTransform.position = Vector3.zero;
274             paddleTransform.localScale =
new Vector3(config.paddleWidth, paddleHeight);
275         }
276
277         
private void GenerateBall()
278         {
279             
if (ball == null)
280             {
281                 ball =
new GameObject("Ball");
282                 ballTransform = ball.transform;
283
284                 
var ballRenderer = ball.AddComponent<SpriteRenderer>();
285                 ballRenderer.sprite = whiteSprite;
286                 ballRenderer.color = Color.black;
287
288                 
var ballCollider = ball.AddComponent<CircleCollider2D>();
289                 ballCollider.radius = ballRadius;
290                 ballCollider.sharedMaterial = bouncyMaterial;
291
292                 ballRigidbody = ball.AddComponent<Rigidbody2D>();
293                 ballRigidbody.gravityScale =
0;
294                 ballRigidbody.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
295                 ballRigidbody.constraints = RigidbodyConstraints2D.FreezeRotation;
296             }
297
298             ballTransform.position = Vector3.up;
299             ballTransform.localScale =
new Vector3(config.ballSize, config.ballSize);
300             ballRigidbody.velocity = Vector2.zero;
301             KickBallInRandomDirection();
302         }
303
304         
private void KickBallInRandomDirection()
305         {
306             Vector2 direction = Random.Range(-
0.5f, 0.5f)*Vector2.right + Vector2.up;
307             ballRigidbody.AddForce(direction*ballForce);
308         }
309     }
310 }


Gõ tìm kiếm nhanh...